/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.fw.util;

import java.lang.reflect.Modifier;

public class ReflectionHelper
{
	/**
	 * Get the fully qualified name of given class. If this is an inner class, replace the dollar ($) symbol
	 * with a dot.
	 *
	 * @param clazz
	 *            Class object
	 * @return fully qualified name of clazz, replacing all occurrences of '$' with a '.'.
	 */
	public static String getClassName(Class<?> clazz)
	{
		return getClassName(clazz, false);
	}

	/**
	 * Get the qualified or unqualified name of the given class depending upon the value of unqualified
	 * parameter. If this is an inner class, replace the dollar ($) symbol with a dot.
	 *
	 * @param clazz
	 *            Class object
	 * @param unqualified
	 *            value to determine if a qualified (false) or unqualified (true) class name should be
	 *            returned
	 * @return Qualified or unqualified name of the given class depending upon the value of unqualified
	 *         parameter.
	 */
	public static String getClassName(Class<?> clazz, boolean unqualified)
	{
		if("void".equals(clazz.getName()) || Void.class.equals(clazz)) return "void";

		String className = clazz.getName().replace('$', '.');

		if(unqualified)
		{
			int lastDot = className.lastIndexOf('.');
			if(lastDot > -1) className = className.substring(lastDot + 1);
		}

		return className;
	}

	/**
	 * Creates instances of the clazz parameter. If the clazz parameter is an interface or an abstract class a
	 * known subclass is instantiated. This method does not cover all areas, and will continue to support more
	 * known interfaces and abstract classes.
	 *
	 * @param clazz
	 *            Class or interface name that needs to be instantiated
	 * @return newly created instance of the clazz parameter
	 * @throws RuntimeException
	 *             if instantiation problem occurs or if a concrete class for the interface or the abstract
	 *             class cannot be located.
	 */
	public static <T> T createInstance(Class<T> clazz)
	{
		try
		{
			return (T) getConcreteClass(clazz).newInstance();
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Guesses a concrete class name for the clazz parameter. If clazz itself is a concrete class, the name of
	 * the same class is returned. If clazz is an abstract class or an interface, this method looks through
	 * known concrete subclasses or implementation and returns the best match. This method does not cover all
	 * areas, and will continue to support more known interfaces and abstract classes.
	 *
	 * @param clazz
	 *            Class or Interface for which the concrete class name is required
	 * @return Name of the concrete class for the given clazz parameter
	 */
	public static String getConcreteClassName(Class<?> clazz)
	{
		return getClassName(getConcreteClass(clazz));
	}

	/**
	 * Creates the best guessed concrete class for the clazz parameter. If clazz itself is a concrete class, the name of
	 * the same class is returned. If clazz is an abstract class or an interface, this method looks through
	 * known concrete subclasses or implementation and returns the best match. This method does not cover all
	 * areas, and will continue to support more known interfaces and abstract classes.
	 *
	 * @param clazz
	 *            Class or Interface for which the concrete class name is required
	 * @return The concrete class for the given clazz parameter
	 */
	public static Class<?> getConcreteClass(Class<?> clazz)
	{
		Class<?> concreteClass = clazz;
		if(
				! clazz.isArray() &&
				! clazz.isEnum() &&
				(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()))
				)
		{
			if(clazz.equals(java.util.List.class) || clazz.equals(java.util.AbstractList.class))
				concreteClass = java.util.ArrayList.class;
			else if(clazz.equals(java.util.Set.class) || clazz.equals(java.util.AbstractSet.class))
				concreteClass = java.util.HashSet.class;
			else if(clazz.equals(java.util.Queue.class) || clazz.equals(java.util.AbstractQueue.class))
				concreteClass = java.util.PriorityQueue.class;
			else if(clazz.equals(java.util.Collection.class))
				concreteClass = java.util.ArrayList.class;
			else if(clazz.equals(java.util.Map.class) || clazz.equals(java.util.AbstractMap.class))
				concreteClass = java.util.HashMap.class;
		}

		return concreteClass;
	}
}
